/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_httpd.htm
 *
 * Zhiqim Httpd is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.httpd.context;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;

import org.zhiqim.httpd.HttpContext;
import org.zhiqim.httpd.HttpContextLoader;
import org.zhiqim.httpd.HttpEntity;
import org.zhiqim.httpd.HttpException;
import org.zhiqim.httpd.HttpExecutor;
import org.zhiqim.httpd.HttpHandler;
import org.zhiqim.httpd.HttpHeader;
import org.zhiqim.httpd.HttpRequest;
import org.zhiqim.httpd.HttpResource;
import org.zhiqim.httpd.HttpResponse;
import org.zhiqim.httpd.HttpSender;
import org.zhiqim.httpd.HttpServer;
import org.zhiqim.httpd.HttpSessionManager;
import org.zhiqim.httpd.HttpdConstants;
import org.zhiqim.httpd.entities.CrossdomainEntity;
import org.zhiqim.httpd.entities.FaviconEnitiy;
import org.zhiqim.httpd.entities.NotFoundEntity;
import org.zhiqim.httpd.entities.OptionsEntity;
import org.zhiqim.httpd.entities.ServiceResourceEntity;
import org.zhiqim.httpd.websocket.HttpWebSocketManager;
import org.zhiqim.kernel.Z;
import org.zhiqim.kernel.config.Group;
import org.zhiqim.kernel.config.Item;
import org.zhiqim.kernel.model.maps.HashMapSO;
import org.zhiqim.kernel.util.Arrays;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Files;
import org.zhiqim.kernel.util.Ints;
import org.zhiqim.kernel.util.Longs;
import org.zhiqim.kernel.util.Strings;
import org.zhiqim.kernel.util.Validates;

/**
 * 静态上下文环境，实现基本的静态环境，允许子类实现不同的环境功能
 *
 * @version v1.0.0 @author zouzhigang 2014-3-21 新建与整理
 */
public class StaticContext implements HttpContext, HttpdConstants
{
    protected static final NotFoundEntity _notFound = new NotFoundEntity();
    
    protected String id;
    protected volatile boolean isRunning;
    protected HttpServer server;
    protected ClassLoader contextLoader;
    protected HashSet<String> contextDomains = new HashSet<>();
    protected String contextPath;
    protected String encoding;
    
    protected HttpResource resource;
    protected final OptionsEntity options = new OptionsEntity();
    protected List<HttpResource> cResourceList = new ArrayList<HttpResource>();
    
    protected boolean cookieUse = true;
    protected String cookieDomain;
    protected int cookiePort;
    protected String cookiePath;
    protected boolean cookieForce = false;
    protected boolean isEncodeable = false;
    
    protected String welcomeUrl;
    protected String notFoundUrl;
    protected int maxContentLength;
    protected int chunkSize;
    protected int expires;
    
    protected HashMapSO attributes = new HashMapSO();
    protected List<HttpHandler> handlers = new ArrayList<HttpHandler>();
    protected List<String> filters = new ArrayList<String>(2);
    
    protected HttpHandler defaultHandler;
    
    public StaticContext()
    {
        this.encoding = _UTF_8_;
        ClassLoader parent = Thread.currentThread().getContextClassLoader();
        this.contextLoader = new HttpContextLoader(parent);
    }
    
    @Override /** 创建 */
    public boolean create(Group group) throws Exception
    {       
        //1.设置contextId
        this.id = group.getId();
        
        //2.设置context资源信息，[path必须，domain可选]
        this.contextPath = group.getString(_CONTEXT_PATH_);
        Asserts.as(Validates.isNotEmpty(contextPath)?null:"HttpContext["+id+"]的未找到[path]配置项");
        
        String contextDomain = group.getString(_CONTEXT_DOMAIN_, null);
        if (Validates.isNotEmptyBlank(contextDomain))
        {//把域名排重放到HashSet中
            String[] domains = Arrays.toStringArray(contextDomain);
            for (String d : domains)
            {
                this.contextDomains.add(d);
            }
        }
        
        //验证是否有相同的
        server.chkContextDomainPath(this.contextDomains, this.contextPath);
        
        //3.设置资源目录和类型，为null表示不支持资源，如仅提供RMI的服务
        String resourceType = group.getString(_CONTEXT_RESOURCE_TYPE_, null);
        String resourcePath = group.getString(_CONTEXT_RESOURCE_PATH_, null);
        this.resource = new HttpResource(resourceType, resourcePath);
        
        //4.设置[/favion.ico和/crossdomain.xml][/service/res/*]默认处理器
        this.addHandler(new FaviconEnitiy(this));
        this.addHandler(new CrossdomainEntity(this));
        this.addHandler(new ServiceResourceEntity());
        
        //5.设置最大文件大小、分块大小、未找到缺省页，欢迎页
        this.setWelcomeUrl(group.getString(_CONTEXT_WELCOME_URL_, null));
        this.setNotFoundUrl(group.getString(_CONTEXT_NOT_FOUND_URL_, null));
        this.maxContentLength = group.getInt(_CONTEXT_MAX_CONTENT_LEN_, _MAX_CONTENT_LEN_);
        this.chunkSize = group.getInt(_CONTEXT_CHUNK_SIZE_, _MAX_CHUNKED_SIZE_);
        this.expires = group.getInt(_CONTEXT_EXPIRES_, _MAX_EXPIRES_);
        this.options.setHeaders(group.getString(_CONTEXT_OPTIONS_HEADERS_, null));
        this.options.setMethods(group.getString(_CONTEXT_OPTIONS_METHODS_, null));
        this.options.setOrigin(group.getString(_CONTEXT_OPTIONS_ORIGIN_, null));
        this.options.setMaxAge(group.getInt(_CONTEXT_OPTIONS_MAX_AGE_, 86400));
        
        //6.设置过滤模式
        String filterPattern = group.getString(_CONTEXT_FILTER_);
        if (Validates.isNotEmpty(filterPattern))
        {
            String[] filters = Arrays.toStringArray(filterPattern);
            for (String filter : filters)
            {
                addFilter(filter);
            }
        }
        
        //7.把配置属性都添加属性表中
        for (Item item : group.list())
        {
            setAttribute(item.getKey(), item.getString());
        }
        
        //创建成功
        this.isRunning = true;
        return true;
    }
    
    @Override /** 销毁 */
    public void destroy()
    {
        if (!isRunning)
            return;
        
        this.isRunning = false;
        
        this.attributes.clear();
        this.handlers.clear();
        this.filters.clear();
    }
    
    /*********************************************************************/
    //上下文环境基本属性相关方法
    /*********************************************************************/

    public String getId()
    {
        return id;
    }
    
    public boolean isRunning()
    {
        return isRunning;
    }
    
    public void setServer(HttpServer server)
    {
        this.server = server;
    }
    
    public void setEncoding(String encoding)
    {
        this.encoding = Strings.trimEmpty(encoding, _UTF_8_);
    }
    
    public String getResourcePath()
    {
        return resource.getResourcePath();
    }
    
    public HttpServer getServer()
    {
        return server;
    }
    
    public HashSet<String> getContextDomains()
    {
        return contextDomains;
    }
    
    public String getContextPath()
    {
        return contextPath;
    }

    /** 获取上下文环境下绝对路径转为根环境下的绝对路径，如contextPath=/doc,path=/index.htm，得到/doc/index.htm */
    public String getRootPath(String path)
    {
        path = Strings.trim(path);
        if (!Strings.startsWith(path, "/"))
            return path;//相对路径不处理
        
        return Strings.trimRight(contextPath, "/") + path;
    }
    
    public String getRealPath(String path)
    {
        Asserts.as(resource.getResourcePath() != null?null:"系统未配置资源根目录，不支持该方法查找地址");
        
        path = Files.toLinuxPath(Strings.trim(path));
        if (Validates.isEmpty(path) || "/".equals(path))
            return resource.getResourcePath();
        
        return resource.getResourcePath() + "/" + Strings.removeStartsWith(path, "/");
    }
    
    public boolean isClasspath()
    {
        return resource.isClasspath();
    }
    
    /** 通过上下文环境加载类 */
    public synchronized Class<?> loadClass(String className) throws ClassNotFoundException
    {
        return contextLoader.loadClass(className);
    }
    
    /*********************************************************************/
    //上下文环境组件资源相关
    /*********************************************************************/
    
    /**
     * 增加组件资源
     * 
     * @param resourceIndex 资源查找索引，从小到大查找
     * @param resourceType  资源类型classpath|directory
     * @param resourcePath  资源地址，如/com/zhiqim/example或./example
     * @return              =true表示成功，=false表示已存在
     */
    public boolean addComponentResource(int resourceIndex, String resourceType, String resourcePath)
    {
        HttpResource cResource = new HttpResource(resourceIndex, resourceType, resourcePath);
        if (cResourceList.contains(cResource))
            return false;
        
        cResourceList.add(cResource);
        Collections.sort(cResourceList, HttpResource._resourceCompor);
        return true;
    }
    
    /**
     * 获取该资源内的资源字符串
     * 
     * @param path          请求
     * @return              =null表示不在该资源内,!=null表示在该资源内并读取到内容
     * @throws IOException  异常
     */
    public String getResourceString(String path) throws IOException
    {
        return getResourceString(path, _UTF_8_);
    }
    
    /**
     * 获取该资源内的资源字符串
     * 
     * @param path          请求
     * @param encoding      编码
     * @return              =null表示不在该资源内,!=null表示在该资源内并读取到内容
     * @throws IOException  异常
     */
    public String getResourceString(String path, String encoding) throws IOException
    {
        String value = null;
        if (resource != null)
        {
            value = resource.getResourceString(path, encoding);
            if (value != null)
                return value;
        }

        //查找是否配置了组件资源目录列表
        for (HttpResource cResource : cResourceList)
        {
            value = cResource.getResourceString(path, encoding);
            if (value != null)
                return value;
        }
        
        return value;
    }
    
    /**
     * 读取资源目录下的文件名和目录名，目录名以/结尾，用于显示目录下列表
     * 
     * @param folderPath    目录地址
     * @return              名称列表
     * @throws IOException  异常
     */
    public List<String> getResourceNameList(String folderPath) throws IOException
    {
        List<String> nameList = new ArrayList<>();
        if (resource != null)
        {
            nameList.addAll(resource.getResourceNameList(folderPath));
        }

        //查找是否配置了组件资源目录列表
        for (HttpResource cResource : cResourceList)
        {
            nameList.addAll(cResource.getResourceNameList(folderPath));
        }
        
        return nameList;
    }
    
    /*********************************************************************/
    //上下文环境过滤和匹配相关
    /*********************************************************************/
    
    /**
     * 增加过滤路径,规则同Handler匹配规则,不允许增加缺省/的过滤
     * 
     * @param match 匹配字符串
     * @return      =true表示增加成功，=false表示增加失败
     */
    public boolean addFilter(String match)
    {
        if ("/".equals(match) || !isValidMatch(match))
            return false;
            
        filters.add(match);
        return true;
    }
    
    /**
     * 设置缺省Handler，除去匹配之后提供的/*处理器
     * 
     * @param handler   处理器
     */
    public void setDefaultHandler(HttpHandler handler)
    {
        this.defaultHandler = handler;
    }
    
    /** 
     * 增加Handler处理器,规则如下：
     * 
     * 1.精确匹配,/开头后加字符串,如/match, /match.do
     * 2.最长路径匹配,使用/*标识,如/match/*
     * 3.扩展匹配,不允许出现/字符,如*.do, *.action
     * 4.缺省匹配,/字符串,当查询前三个不成功时,跳转到ResourceHandler处理,如果资源未找到,则转到缺省匹配
     * 5.如果前四种都失败,返回系统404页面
     * 
     * @param match     匹配字符串
     * @param handler   处理器
     * @return          =true表示增加成功，=false表示增加失败
     */
    public boolean addHandler(HttpHandler handler)
    {
        handlers.add(handler);
        return true;
    }
    
    /** 匹配对应的Handler */
    public HttpHandler getMatchHandler(String pathInContext)
    {
        for (HttpHandler handler : handlers)
        {
            if (handler.isMatch(pathInContext))
                return handler;
        }
        
        return defaultHandler;
    }
    
    /** 是否过虑匹配 */
    public boolean isFilterPath(String pathInContext)
    {
        for (String filter : filters)
        {
            if (filter.equals(pathInContext))
                return true;//精确
            
            if (filter.endsWith("/*"))
            {
                filter = filter.substring(0, filter.length()-1);
                if (pathInContext.startsWith(filter))
                    return true;//路径
            }
            
            if (filter.startsWith("*."))
            {
                filter = filter.substring(1);
                if (pathInContext.endsWith(filter))
                    return true;//扩展
            }
            
            int ind = filter.indexOf("/*.");
            if (ind != -1)
            {//指定路径的扩展,如/service/*.js
                String pathPrefix = filter.substring(0, ind+1);// /service/
                String pathSuffix = filter.substring(ind+2);// .js
                if (pathInContext.startsWith(pathPrefix) && pathInContext.endsWith(pathSuffix))
                    return true;
            }
        }
        
        return false;
    }

    /** 是否是有效的匹配 */
    protected boolean isValidMatch(String match)
    {
        if (match == null)
            return false;
        
        if ("/".equals(match))
            return true;//缺省匹配
        
        int times = Strings.getTimes(match, '*');
        if (times == 0 && match.startsWith("/"))
            return true;//精确匹配,如/index.htm
        
        if (times == 1 && match.startsWith("/") && match.endsWith("/*"))
            return true;//路径匹配,如/service/*
        
        if (times == 1 && (match.indexOf("/*.") != -1 || match.startsWith("*.")) && !match.endsWith("."))
            return true;//扩展匹配,如*.htm,或指定路径的扩展匹配/service/*.htm
        
        return false;
    }
    
    /*********************************************************************/
    //上下文环境处理器，根据过滤、匹配和组件资源一起配合检查处理
    /*********************************************************************/
    
    /** 
     * 默认处理请求，子类可以重写，默认查询Handler，如果查到对应由Handler处理，否则认为由ResourceHandler处理
     * 
     * @param request           请求
     * @param response          响应
     * @exception HttpException HTTP异常
     * @exception IOException   IO异常
     */
    public void handle(HttpRequest request, HttpResponse response)throws HttpException, IOException
    {        
        //6.第六步，查找处理器
        String pathInContext = request.getPathInContext();
        
        //查询是否过滤,如果在过滤里那么由缺省Handler处理
        if (isFilterPath(pathInContext))
        {
            response.sendError(_403_FORBIDDEN_);
            return;
        }

        //查找匹配
        HttpHandler handler = getMatchHandler(pathInContext);
        if (handler instanceof HttpExecutor)
        {
            ((HttpExecutor)handler).handle(request, response);
            return;
        }
        else if (handler instanceof HttpEntity)
        {
            ((HttpEntity)handler).handle(request, response);
            return;
        }
        
        //查询不到则跳转到处理文件资源
        handleResource(request, response);
    }
    
    /**
     * 公共处理文件资源，该方法可以在匹配到的Handler中发现子匹配失败时回跳到文件资源处理中再检查一次
     * 
     * @param request           请求
     * @param response          响应
     * @exception HttpException HTTP异常
     * @exception IOException   IO异常
     */
    public void handleResource(HttpHeader header, HttpSender sender)throws HttpException, IOException
    {
        //查找是否配置了资源目录
        if (resource != null)
        {
            if (resource.handleResource(header, sender))
                return;
        }

        //查找是否配置了组件资源目录列表
        for (HttpResource cResource : cResourceList)
        {
            if (cResource.handleResource(header, sender))
                return;
        }
        
        //最后未找到处理
        _notFound.handle(header, sender);
    }
    
    /*********************************************************************/
    //上下文环境自定义属性相关方法
    /*********************************************************************/
    
    public void setAttribute(String key, Object value)
    {
        attributes.put(key, value);
    }
    
    public <T> void setAttribute(Class<T> key, T value)
    {
        attributes.put(key.getName(), value);
    }
    
    public boolean hasAttribute(String key)
    {
        return attributes.containsKey(key);
    }
    
    public HashMapSO getAttributes()
    {
        return attributes;
    }
    
    public Object getAttribute(String key)
    {
        return attributes.get(key);
    }
    
    @SuppressWarnings("unchecked")
    public <T> T getAttribute(Class<T> key)
    {
        return (T)attributes.get(key.getName());
    }
    
    public Object getAttribute(String key, Object defaultValue)
    {//这里不处理name和version，由缺省指定
        Object value = getAttribute(key);
        return (value == null)?defaultValue:value;
    }
    
    public String getContextName()
    {
        String name = getAttributeString(_NAME_);
        return name == null?Z.conf().getName():name;
    }
    
    public String getContextVersion()
    {
        String version = getAttributeString(_VERSION_);
        return version == null?Z.conf().getVersion():version;
    }
    
    public String getAttributeString(String key)
    {
        return getAttributeString(key, null);
    }
    
    public String getAttributeString(String key, String defaultValue)
    {
        Object value = getAttribute(key);
        return (value == null)?defaultValue:Strings.trim(String.valueOf(value));
    }
    
    public int getAttributeInt(String key)
    {
        return getAttributeInt(key, -1);
    }
    
    public int getAttributeInt(String key, int defaultValue)
    {
        return Ints.toInt(getAttribute(key), defaultValue);
    }
    
    public long getAttributeLong(String key)
    {
        return getAttributeLong(key, -1);
    }
    
    public long getAttributeLong(String key, long defaultValue)
    {
        return Longs.toLong(getAttribute(key), defaultValue);
    }
    
    public boolean getAttributeBoolean(String key, boolean defaultValue)
    {
        Object value = getAttribute(key);
        if (value == null)
            return defaultValue;
        else if (value instanceof Boolean)
            return (Boolean)value;
        else if (value instanceof String)
            return Boolean.parseBoolean((String)value);
        else
            return defaultValue;
    }
    
    /**********************************************************************************/
    //静态上下文环境四项配置
    /**********************************************************************************/

    /** 获取欢迎页 */
    public String getWelcomeUrl()
    {
        return welcomeUrl;
    }
    
    /** 获取404页 */
    public String getNotFoundUrl()
    {
        return notFoundUrl;
    }
    
    /** 获取NotFoundHandler */
    public HttpEntity getNotFoundHandler()
    {
        return _notFound;
    }
    
    /** 获取OptionsHandler */
    public HttpEntity getOptionsHandler()
    {
        return options;
    }
    
    /** 获取上行最大内容长度 */
    public int getMaxContentLength()
    {
        return maxContentLength;
    }
    
    /** 获取设置的分块大小 */
    public int getChunkSize()
    {
        return chunkSize;
    }
    
    /** 获取静态文件有效期 */
    public int getExpires()
    {
        return expires;
    }
    
    public void setWelcomeUrl(String welcomeUrl)
    {
        this.welcomeUrl = (Validates.isEmptyBlank(welcomeUrl))?null:Strings.addStartsWith(welcomeUrl, "/");
    }
    
    public void setNotFoundUrl(String notFoundUrl)
    {
        this.notFoundUrl = (Validates.isEmptyBlank(notFoundUrl))?null:Strings.addStartsWith(notFoundUrl, "/");
    }

    public String getDefaultEncoding()
    {
        return encoding;
    }
    
    /**********************************************************************************/
    //静态上下文环境Cooke使用默认值
    /**********************************************************************************/
    
    /** 子类可以重写该方法 */
    public boolean isCookieUse()
    {
        return cookieUse;
    }
    
    public String getCookieDomain()
    {
        return cookieDomain;
    }
    
    public int getCookiePort()
    {
        return cookiePort;
    }

    public String getCookiePath()
    {
        return contextPath;
    }
    
    public boolean isCookieForce()
    {
        return cookieForce;
    }
    
    public boolean isEncodeable()
    {
        return isEncodeable;
    }
    
    public void setCookieUse(boolean cookieUse)
    {
        this.cookieUse = cookieUse;
    }
    
    public void setCookieDomain(String cookieDomain)
    {
        this.cookieDomain = cookieDomain;
    }
    
    public void setCookiePort(int cookiePort)
    {
        this.cookiePort = cookiePort;
    }
    
    public void setCookiePath(String cookiePath)
    {
        this.cookiePath = cookiePath;
    }
    
    public void setCookieForce(boolean cookieForce)
    {
        this.cookieForce = cookieForce;
    }
    
    public void setEncodeable(boolean isEncodeable)
    {
        this.isEncodeable = isEncodeable;
    }
    
    /**********************************************************************************/
    //静态上下文环境Session不支持
    /**********************************************************************************/
    
    public HttpSessionManager getSessionManager()
    {
        return null;
    }
    
    public void invalidateSession(String sessionId)
    {
    }
    
    /*********************************************************************/
    //静态上下文环境WebSocket不支持
    /*********************************************************************/
    
    @Override
    public HttpWebSocketManager getWebSocketManager()
    {//不支持
        return null;
    }
}
